import 'dart:async';
import 'dart:collection';
import 'dart:core';

import 'package:im_app/im/service/ImConfigService.dart';
import 'package:im_app/im/service/ImDepartmentService.dart';
import 'package:im_app/im/service/ImGroupService.dart';
import 'package:im_app/im/service/ImMessageRefService.dart';
import 'package:im_app/im/service/ImMessageSendService.dart';
import 'package:im_app/im/service/ImSensitiveWordService.dart';
import 'package:im_app/im/service/ImUserService.dart';
import 'package:im_app/im/service/ImMessageService.dart';
import 'package:im_app/im/pojo/ImPojo.dart';
import 'package:im_app/im/socket/dto/business/PullData2.dart';
import 'package:im_app/im/uploadAndDownload/BatchDownloadService.dart';
import 'package:im_app/im/util/DateUtil.dart';
import 'package:im_app/im/util/ImSingleton.dart';
import 'package:im_app/im/util/UiUtil.dart';
import 'package:logger/logger.dart';

import '../dao/ImGroupDao.dart';
import '../dao/ImMessageDao.dart';
import '../dao/ImSessionDao.dart';
import '../dao/ImSettingDao.dart';
import '../dao/ImUserDao.dart';
import '../pojo/Enums.dart';
import '../socket/dto/base/Req.dart';
import '../socket/dto/business/Message.dart';
import '../socket/dto/business/PullData.dart';
import '../socket/dto/business/SendData.dart';
import '../socket/service/RpcPullDataService.dart';
import '../socket/service/RpcSendDataService.dart';
import '../uploadAndDownload/UploadAndDownloadDto.dart';
import 'BaseService.dart';
import 'ImCollectService.dart';
import 'ImModuleService.dart';
import 'ImSessionService.dart';

typedef SynDataFunction = Future<int> Function(DateTime? startDateTime, bool isLatestChange);

class SynDataService extends BaseService {
  final _logger = Logger(
    printer: PrettyPrinter(),
  );

  final _pullDataService = RpcPullDataService();

  Future<int> synData(String userId) async {
    final userDao = ImUserDao();
    int total = 0;
    var date = DateTime.now();
    try {
      total += await synCommonData(userId);
      total += await _synMessages(userId);            // 同步消息
      total += await _synMessageAction(userId);       // 同步消息事件
      total += await _synLabels(userId);              // 同步标签
      total += await _synCollects(userId);            // 同步收藏
      total += await _synFeedbacks(userId);           // 同步反馈
      total += await _synSensitiveWords(userId);      // 同步敏感词

      var imUser = await userDao.getByUserId(userId);
      UiUtil.setLoginUser(imUser!);
    }  catch (e, stackTrace) {
      _logger.e("synData error: ", error: e, stackTrace: stackTrace);
    } finally {
      var times = DateTime.now().difference(date).inMilliseconds;
      _logger.i("synData total time: ${times}, total size: ${total}");
    }

    return total;
}

  // 同步数据：机构，群，用户
  Future<int> synCommonData(String userId) async {
    _logger.i("synCommonData start!!!!!: " + userId);
    int total = 0;
    total += await _synConfigs(userId);              // 同步配置
    total += await _synDepartments(userId);          // 同步机构
    total += await _synUsers();                      // 同步用户
    total += await _synGroups(userId);               // 同步群
    total += await _synModules(userId);              // 同步模块
    total += await _sendNeedGroups(userId);          // 发送未成功的群
    total += await _sendNeedMessages(userId);        // 发送未成功的消息
    total += await _sendMessageActionStatus(userId); // 发送未同步成功的消息事件
    total += await _synSessions(userId);             // 同步消息组
    return total;
  }

  // 同步消息
  Future<int> _synMessages(String userId) async {
    final sessionDao = ImSessionDao();
    return await _synData(userId, SynDataType.SynMessage, (startDatetime, isLatestChange) async {
      int total = 0;
      var date = DateTime.now();
      List<ImSession> sessions = await sessionDao.getByUserIdAndSize(userId, 500);
      total += await _synMessageBySession(sessions, startDatetime, userId);
      var times = DateTime.now().difference(date).inMilliseconds;
      _logger.i("synData message data time: $times, total: $total");
      return total;
    });
  }

  Future<int> _synConfigs(String userId) async {
    return _synData("", SynDataType.SynModule, (startDatetime, isLatestChange) async {
      int total = 0;

      var configService = ImConfigService();

      int pageSize = 1000;
      ConfigPullReq configPullReq = ConfigPullReq();
      configPullReq.pageSize = pageSize;
      configPullReq.startDate = startDatetime;

      String? startName = null;
      while(true) {
        configPullReq.startServerName = startName;
        ConfigItemList? configItemList = await _pullDataService.pullConfigList(configPullReq);
        int length = configItemList.configItems.length;
        total += length;
        if (length > 0) {
          await configService.saveOrUpdates(userId, configItemList.configItems);
        }

        if (pageSize == length) {
          startName = configItemList.configItems[length-1].name;
        } else {
          break;
        }
      }
      return total;
    });
  }

  Future<int> _synDepartments(String userId) async {
    return _synData("", SynDataType.SynDepartment, (startDatetime, isLatestChange) async {
      int total = 0;

      var imDepartmentService = new ImDepartmentService();

      int pageSize = 1000;
      DepartmentInfoListReq departmentInfoListReq = new DepartmentInfoListReq();
      departmentInfoListReq.pageSize = pageSize;
      departmentInfoListReq.startDate = startDatetime;
      departmentInfoListReq.isLatestChange = isLatestChange;

      String? startOrgCode = null;
      while(true) {
        departmentInfoListReq.startOrgCode = startOrgCode;
        DepartmentInfoListItem? departmentInfoListItem = await _pullDataService.pullDepartmentInfoList(departmentInfoListReq);
        int length = departmentInfoListItem.departmentInfoItemList.length;
        total += length;
        if (length > 0) {
          await imDepartmentService.saveOrUpdates(userId, departmentInfoListItem.departmentInfoItemList);
        }

        if (pageSize == length) {
          startOrgCode = departmentInfoListItem.departmentInfoItemList[length-1].orgCode;
        } else {
          break;
        }
      }

      return total;
    });
  }

  Future<int> _synUsers() async {
    return await _synData("", SynDataType.SynUser, (startDatetime, isLatestChange) async {
      int total = 0;
      int pageSize = 500;
      String? startUserId = null;
      UserInfoListReq userInfoListReq = new UserInfoListReq();
      userInfoListReq.pageSize = pageSize;
      userInfoListReq.isLatestChange = false;
      userInfoListReq.startDate = startDatetime;

      var imUserService = new ImUserService();

      while(true) {
        userInfoListReq.startUserId = startUserId;
        UserInfoListItem? userInfoListItem = await _pullDataService.pullUserInfoList(userInfoListReq);

        int size = userInfoListItem.userInfoItemList.length;
        total += size;

        await imUserService.saveOrUpdates(userInfoListItem.userInfoItemList);

        if (size == pageSize) {
          startUserId = userInfoListItem?.userInfoItemList[size-1].userId;
        } else {
          break;
        }
      }

      return total;
    });
  }

  Future<int> _synGroups(String userId) async {
    return _synData(userId, SynDataType.SynGroup, (startDatetime, isLatestChange) async {
      int total = 0;
      int pageSize = 200;
      String? startGroupId = null;
      GroupInfoListReq groupInfoListReq = GroupInfoListReq(GroupType.chat);
      groupInfoListReq.startDate = startDatetime;
      groupInfoListReq.isLatestChange = isLatestChange;
      groupInfoListReq.pageSize = pageSize;

      var imGroupService = ImGroupService();

      while(true) {
        groupInfoListReq.startServerId = startGroupId;
        GroupInfoListItem? groupInfoListItem = await _pullDataService.pullGroupInfoList(groupInfoListReq);
        int size = groupInfoListItem.groupInfoItemList.length;
        total += size;

        await imGroupService.saveOrUpdates(userId, groupInfoListItem.groupInfoItemList, false);

        if (size == pageSize) {
          startGroupId = groupInfoListItem.groupInfoItemList[size - 1].groupId;
        } else {
          break;
        }
      }

      return total;
    });
  }

  Future<int> _synModules(String userId) async {
    return _synData(userId, SynDataType.SynModule, (startDatetime, isLatestChange) async {
      int total = 0;
      int pageSize = 200;
      String? startServerName = null;
      ModulePullReq modulePullReq = ModulePullReq();
      modulePullReq.startDate = startDatetime;
      modulePullReq.pageSize = pageSize;

      var moduleService = ImModuleService();

      while(true) {
        modulePullReq.startServerName = startServerName;
        ModuleItemList? moduleItemList = await _pullDataService.pullModuleItemList(modulePullReq);
        int size = moduleItemList.moduleInfoItems.length;
        total += size;

        await moduleService.saveOrUpdates(userId, moduleItemList.moduleInfoItems);

        if (size == pageSize) {
          startServerName = moduleItemList.moduleInfoItems[size - 1].name;
        } else {
          break;
        }
      }

      return total;
    });
  }

  Future<int> _sendNeedGroups(String userId) async {
    var size = 0;
    var pageSize = 1000;

    final groupDao = ImGroupDao();
    var imGroupService = ImGroupService();
    var rpcGroupService = RpcGroupService();
    while(true) {
      var imGroups = await groupDao.getNeedSendGroupsByUserId(userId, pageSize);
      size += imGroups.length;
      for (var imGroup in imGroups) {
        var userIdSet = <String>[];
        imGroup.groupUserList?.forEach((gu)=>userIdSet.add(gu.userId));
        var groupCreateReq = new GroupCreateReq(groupId : imGroup.code, groupName: imGroup.name??'', userIdSet: userIdSet);
        groupCreateReq.groupDesc = imGroup.description;

        var okResp = await rpcGroupService.groupCreate(groupCreateReq);

        await imGroupService.sendSuccess(imGroup, okResp.otherInfo??'');
      }
      if (imGroups.length != pageSize) {
        break;
      }
    }

    return size;
  }

  Future<int> _sendNeedMessages(String userId) async {
    int size = 0;
    int pageSize = 500;
    _logger.i("_sendNeedMessages start");

    final messageDao = ImMessageDao();
    var messageSendService = ImMessageSendService();

    while(true) {
      List<ImMessage> sendNeedMessages = await messageDao.getNeedSendMessagesByUserId(userId, pageSize);
      size += sendNeedMessages.length;

      if (sendNeedMessages.isNotEmpty) {
        await messageSendService.sendMessages(userId, sendNeedMessages);
      }

      if (sendNeedMessages.length != pageSize) {
        break;
      }
    }

    _logger.i("_sendNeedMessages end!!!!");
    return size;
  }

  Future<int> _sendMessageActionStatus(String userId) async {
    var size = 0;
    var pageSize = 1000;
    _logger.i("_sendMessageActionStatus start");

    var imMessageDao = ImMessageDao();
    while(true) {
      var imMessages = await imMessageDao.getNeedSynActionStatusMessagesByUserId(userId, pageSize);
      size += imMessages.length;

      var messageReadMessages = <ImMessage>[];
      var messageWithdrawMessages = <ImMessage>[];
      for (var imMessage in imMessages) {
        if (imMessage.actionStatus == ImMessageActionStatus.read) {
          messageReadMessages.add(imMessage);
        } else if (imMessage.actionStatus == ImMessageActionStatus.withdraw) {
          messageWithdrawMessages.add(imMessage);
        } else {
          _logger.e("同步消息事件不支持这种类型, ${imMessage.actionStatus}");
        }
      }

      await this.sendReadMessages(messageReadMessages);
      await this.sendWithdrawMessages(messageWithdrawMessages);
      if (imMessages.length != pageSize) {
        break;
      }
    }
    _logger.i("_sendMessageActionStatus end");
    return size;
  }

  Future<int> _synSessions(String userId) async {
    return await _synData(userId, SynDataType.SynSession, (startDatetime, isLatestChange) async {
      int total = 0;
      int pageSize = 500;
      String? startServerId;
      var sessionPullReq = SessionPullReq(pageSize);
      sessionPullReq.startDate = startDatetime;
      sessionPullReq.sessionType = SessionType.chat;

      var imSessionService = ImSessionService();

      while(true) {
        sessionPullReq.startServerId = startServerId;
        var sessionItemList = await _pullDataService.pullSessions(sessionPullReq);
        _logger.i("synData sessions ${sessionItemList.sessionItemList.length}");
        int size = sessionItemList.sessionItemList.length;
        if (size > 0) {
          await imSessionService.saveOrUpdates(userId, sessionItemList.sessionItemList);
        }

        if (size == pageSize) {
          startServerId = sessionItemList.sessionItemList[size-1].serverId;
        } else {
          break;
        }
      }

      return total;
    });
  }

  Future<int> _synMessageBySession(List<ImSession> sessions, DateTime? startDatetime, String userId) async {
    if (sessions.isEmpty) return 0;
    _logger.i("SynData message start");
    int pageSize = 500;
    var startServerMessageId = null;
    var sessionIds = HashSet<String>();
    for (var session in sessions) {
      if (session.serverId != null) {
        sessionIds.add(session.serverId!);
      }
    }
    if(sessionIds.isEmpty) return 0;

    int total = 0;

    var messagePullBySessionIdsReq = MessagePullBySessionIdsReq(terminalType: TerminalType.android, pageSize: pageSize,
        sessionIds: sessionIds);
    messagePullBySessionIdsReq.aftDate = startDatetime;

    var messageService = ImMessageService();
    var messageRefService = ImMessageRefService();
    while(true) {
      messagePullBySessionIdsReq.startServerMessageId = startServerMessageId;
      var messageListItem = await _pullDataService.pullMessageBySessionIds(messagePullBySessionIdsReq);
      int length = messageListItem.messageObjectList.length;
      total += length;
      _logger.i("synData message size $length ");

      // 拉取附件消息
      Set<String>? pulledServerIds = messageListItem.messageObjectList.map((mo)=>mo.serverId!).toSet();
      Set<String> serverIdSet = messageListItem.messageObjectList.where((mo)=>mo.isRef())
        .expand((mo)=>mo.refMessageIds!.split(",")).where((serverId)=>!pulledServerIds.contains(serverId))
        .toSet();
      List<MessageObject> messageObjects = await messageRefService.pullRefMessages(userId, serverIdSet.toList());

      List<MessageObject> allMessageObjects = messageListItem.messageObjectList;
      allMessageObjects.addAll(messageObjects);

      List<ImMessage> imMessageList = await messageService.saveOrUpdates(userId, allMessageObjects, true);

      _downloadFile(imMessageList);

      var okListReq = OkListReq(coder: 11, localSeqSet: messageListItem.messageObjectList.map((m)=>m.localSeq!).toSet
        ());

      _pullDataService.okMessages(okListReq);
      if(messageListItem?.messageObjectList.length != pageSize) {
        break;
      } else {
        startServerMessageId = messageListItem.messageObjectList[length - 1].serverId;
    }
      _logger.i("synData message end : $total");
    }

    return total;
  }

  Future<int> _synMessageAction(String userId) async {
    int total = 0;

    return total;
  }

  Future<int> _synLabels(String userId) async {
    int total = 0;

    return total;
  }

  Future<int> _synCollects(String userId) async {
    return _synData("", SynDataType.SynCollect, (startDatetime, isLatestChange) async {
      int total = 0;

      var collectService = ImCollectService();
      var messageRefService = ImMessageRefService();
      var messageService = ImMessageService();

      int pageSize = 200;
      CollectPullReq collectPullReq = CollectPullReq();
      collectPullReq.pageSize = pageSize;
      collectPullReq.startDate = startDatetime;

      String? startServerId = null;
      while(true) {
        collectPullReq.startServerId = startServerId;
        CollectListItem collectListItem = await _pullDataService.pullCollects(collectPullReq);
        int length = collectListItem.collectInfoItems.length;
        total += length;
        if (length > 0) {

          var messageObjects = await messageRefService.pullCollectRefMessages(userId, collectListItem.collectInfoItems);
          await messageService.saveOrUpdates(userId, messageObjects, true);

          await collectService.saveOrUpdates(userId, collectListItem.collectInfoItems);
        }

        if (pageSize == length) {
          startServerId = collectListItem.collectInfoItems[length-1].serverId;
        } else {
          break;
        }
      }
      return total;
    });
  }

  Future<int> _synFeedbacks(String userId) async {
    int total = 0;

    return total;
  }

  Future<int> _synSensitiveWords(String userId) async {
    return _synData("", SynDataType.SynSensitiveWord, (startDatetime, isLatestChange) async {
      int total = 0;

      var imSensitiveWordService = new ImSensitiveWordService();

      int pageSize = 1000;
      String? startServerId = null;
      var sensitiveWordPullReq = new SensitiveWordPullReq(pageSize: pageSize);
      sensitiveWordPullReq.startDate = startDatetime;

      while(true) {
        sensitiveWordPullReq.startServerId = startServerId;
        var sensitiveWordListItem = await _pullDataService.pullSensitiveWords(sensitiveWordPullReq);
        int length = sensitiveWordListItem.sensitiveWordList.length;
        total += length;
        if (length > 0) {
          imSensitiveWordService.saveOrUpdates(userId, sensitiveWordListItem.sensitiveWordList);
        }

        if (pageSize == length) {
          startServerId = sensitiveWordListItem.sensitiveWordList[length-1].serverId;
        } else {
          break;
        }
      }

      return total;
    });
  }

  Future<int> _synData(String userId, SynDataType synDataType, SynDataFunction synData) async {
    _logger.i("_synData ${synDataType.name} start");

    final settingDao = SettingDao();

    var startTime = DateTime.now();
    DateTime? startDate;
    bool isLatestChange = false;
    var imSetting = await settingDao.getByName(userId, synDataType.name);
    if (imSetting != null && imSetting.value != null){
      var value = imSetting?.value;
      startDate = DateTime.parse(value??'');
      isLatestChange = true;
    }
    int size = await synData.call(startDate, isLatestChange);

    DateTime serverDate = (ImSingleton.getInstance().session.getData('serverDate'))! as DateTime;
    if (isLatestChange) {
      await settingDao.update(userId, synDataType.name, DateUtil.formatDate2(serverDate, "yyyy-MM-dd HH:mm"));
    } else {
      await settingDao.insert(userId, synDataType.name, DateUtil.formatDate2(serverDate, "yyyy-MM-dd HH:mm"));
    }
    var times = DateTime.now().difference(startTime).inMilliseconds;
    _logger.i("synData ${synDataType.name} end, time: ${times}, total: ${size}");
    return size;
  }

  Future<void> sendReadMessages(List<ImMessage> readMessages) async {

    return;
  }

  Future<void> sendWithdrawMessages(List<ImMessage> readMessages) async {
    return;
  }

  // 批量下载文件
  void _downloadFile(List<ImMessage> imMessageList) {
    for (var message in imMessageList) {
      BatchDownloadService.getInstance().downFile(message, (FileDownloadResult downResult)=> (ImMessage message,
          FileDownloadResult downResult)=>{});
    }
  }
}
